package com.guaoran.commons.swagger.ui.config;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.guaoran.commons.swagger.ui.IWebSecurityConfigurer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration;

import java.util.ArrayList;
import java.util.List;


/**
 * @author : gucheng
 * @Description : <br/>
 *  swagger 自动配置 如果开启了 <properties>common.swagger.enabled=true</properties> ,则加载 swagger 配置
 * @date :2018/11/15 14:13
 */

/**
 * <p>因为 WebSecurityConfigurerAdapter 的优先级为100，且 security 是按照高优先级为主来实现的。</p>
 * <p>现在改成低优先级会覆盖高优先级 </p>
 * <p>这样可以保证在引用该starter的工程中，如果也引用了`spring-boot-starter-security` 依赖，则以引用该stater的工程为准</p>
 */
@ConditionalOnProperty(name = "common.swagger.enabled", havingValue = "true")
@EnableConfigurationProperties(SwaggerProperties.class)
@Import({Swagger2DocumentationConfiguration.class,PermitAllSecurityConfiguration.class})
public class SwaggerUiAutoConfiguration implements BeanFactoryAware ,IWebSecurityConfigurer {
    private BeanFactory beanFactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * 如果开启了 <properties>common.swagger.enabled=true</properties> ,则加载 swagger 配置
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "common.swagger.enabled", havingValue = "true")
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }
    /**
     * 如果开启了 <properties>common.swagger.enabled=true</properties> ,则加载 swagger 配置
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "common.swagger.enabled", havingValue = "true")
    public Docket controllerApi(SwaggerProperties swaggerProperties) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(getBaseAndExcludePath(swaggerProperties))
                .build();
//                .securitySchemes(Collections.singletonList(apiKey(swaggerProperties)))
//                .securityContexts(securityContexts(swaggerProperties));
    }

    /**
     * 联系人
     * @param swaggerProperties
     * @return
     */
    private Contact contact(final SwaggerProperties swaggerProperties){
        return new Contact(swaggerProperties.getContactName(),
                        swaggerProperties.getContactUrl(),
                        swaggerProperties.getContactEmail());
    }

    /**
     * apiInfo
     * @param swaggerProperties
     * @return
     */
    private ApiInfo apiInfo(final SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
            .title(swaggerProperties.getTitle())
            .description(swaggerProperties.getDescription())
            .version(swaggerProperties.getVersion())
            .contact(contact(swaggerProperties))
            .license(swaggerProperties.getLicense())
            .licenseUrl(swaggerProperties.getLicenseUrl())
            .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
            .build();
    }
    /**
     * 获得 basePath 、 excludePath 地址
     * @param swaggerProperties
     * @return
     */
    private Predicate<String> getBaseAndExcludePath(final SwaggerProperties swaggerProperties){
        // 当没有配置任何 base-path 的时候，解析/**
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add("/**");
        }
        List<Predicate<String>> basePath = new ArrayList();
        for (String path : swaggerProperties.getBasePath()) {
            basePath.add(PathSelectors.ant(path));
        }
        // exclude-path 处理
        List<Predicate<String>> excludePath = new ArrayList<>();
        for (String path : swaggerProperties.getExcludePath()) {
            excludePath.add(PathSelectors.ant(path));
        }
        return Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath));
    }



    @SuppressWarnings("deprecation")
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(SwaggerProperties.class)
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(SwaggerProperties swaggerProperties) {
        return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
                .username(swaggerProperties.getSecurityUserName())
                .password(swaggerProperties.getSecurityUserPassword())
                // 增加可以自定义角色名称
                .authorities("ROLE_"+swaggerProperties.getSecurityRoleName()).build());
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // spring boot项目中出现不能加载iframe
        http.headers().frameOptions().disable();

        SwaggerProperties swaggerProperties = (SwaggerProperties) beanFactory.getBean("swaggerProperties");
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry = http.authorizeRequests();
        // 如果不是对所有人开放
        if(!swaggerProperties.isSecurityOpenToAll()){
            // 访问 /swagger-ui.html 权限认证的角色默认是 SWAGGER ，其他的放行
            expressionInterceptUrlRegistry.mvcMatchers("/swagger-ui.html","/v2/api-docs").hasRole(swaggerProperties.getSecurityRoleName());
        }
        expressionInterceptUrlRegistry.antMatchers("/**").permitAll()
        .and()
        .cors()
        .and()
        .httpBasic()
        .and().csrf().disable();
    }
    @Bean
    public HttpFirewall allowUrlEncodedPercentHttpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedPercent(true);
        return firewall;
    }


// delete for reason ：应该是我理解的方式有问题，该方法不能在访问接口时进行权限认证，目前采用 security

//    /**
//     * 配置基于 ApiKey 的鉴权对象
//     * @return
//     */
//    private ApiKey apiKey(final SwaggerProperties swaggerProperties) {
//        return new ApiKey(swaggerProperties.getAuthorizationName(),
//                swaggerProperties.getAuthorizationKeyName(),
//                ApiKeyVehicle.HEADER.getValue());
//    }

//    /**
//     * 配置默认的全局鉴权策略的开关，以及通过正则表达式进行匹配；默认 ^.*$ 匹配所有URL
//     * 其中 securityReferences 为配置启用的鉴权策略
//     * @return
//     */
//    private List<SecurityContext> securityContexts(final SwaggerProperties swaggerProperties) {
//        return Collections.singletonList(SecurityContext.builder()
//                .securityReferences(defaultAuth(swaggerProperties))
//                .forPaths(PathSelectors.regex(swaggerProperties.getAuthorizationAuthRegex()))
//                .build());
//    }

//    /**
//     * 配置默认的全局鉴权策略；其中返回的 SecurityReference 中，reference 即为ApiKey对象里面的name，保持一致才能开启全局鉴权
//     * @return
//     */
//    private List<SecurityReference> defaultAuth(final SwaggerProperties swaggerProperties) {
//        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
//        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
//        authorizationScopes[0] = authorizationScope;
//        return Collections.singletonList(SecurityReference.builder()
//                .reference(swaggerProperties.getAuthorizationName())
//                .scopes(authorizationScopes).build());
//    }
}
